package com.jy.rhin.valid;


import com.jy.common.StringUtil;
import com.jy.framework.base.entity.Result;
import com.jy.rhin.application.cda.QianZhiJDApplication;
import com.jy.rhin.facade.cda.dto.QianZhiJDPZWJDto;
import com.jy.rhin.infra.cda.CDAException;
import com.jy.rhin.infra.cda.XPath;
import com.jy.rhin.infra.cda.XValid;
import com.jy.rhin.infra.cda.util.ReflectUtils;
import com.jy.rhin.model.*;
import com.jy.rhin.model.body.ertongjktj.ErTongJKTJ;
import com.jy.rhin.model.body.siwangyxzm.SiWangYXZM;
import com.jy.rhin.parse.ParserFactory;
import com.jy.rhin.parse.Selector;
import com.jy.rhin.utils.ApplicationContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.nodes.Element;
import org.jsoup.parser.Parser;
import org.seimicrawler.xpath.JXDocument;
import org.seimicrawler.xpath.JXNode;
import org.springframework.util.ObjectUtils;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.File;
import java.lang.reflect.Field;
import java.util.*;

/**
 * @Author 姚俊
 * @Date 2023/2/27 8:53
 * @Description CDA文档校验器包装类
 */
@Slf4j
public class CdaValidator {

    static SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
    static String xsdPath = "/opt/xsd/sdschemas/CDA.xsd";
    static Map<String, Class<? extends ClinicalDocumentData>> docDataModels;

    static {
        docDataModels = new HashMap<>();
        docDataModels.put("HSDC00.01", JianKangTJ.class);
        docDataModels.put("HSDC00.02", MenZhenZY.class);
        docDataModels.put("HSDB02.01", ChanQianSCSF.class);
        docDataModels.put("HSDB02.02", ChanQianSF.class);
        docDataModels.put("HSDB02.03", ChanHouFS.class);
        docDataModels.put("HSDB02.04", ChanHouJC.class);
        docDataModels.put("HSDB03.01", YuFangJZ.class);
        docDataModels.put("HSDB03.02", ChuanRanBBG.class);
        docDataModels.put("HSDB04.01", GaoXueYSF.class);
        docDataModels.put("HSDB04.02", TangNiaoBSF.class);
        docDataModels.put("HSDB04.03", ZhongXingJSBDJ.class);
        docDataModels.put("HSDB04.04", ZhongXingJSBSF.class);
        docDataModels.put("HSDA00.01", GeRenJBXXDJ.class);
        docDataModels.put("HSDB01.02", XinShengErJTFS.class);
        docDataModels.put("HSDB01.01", ChuShengYXZM.class);
        docDataModels.put("HSDB03.03", SiWangYXZM.class);
        docDataModels.put("HSDC00.03", ZhuYuanZY.class);
        docDataModels.put("HSDC00.05", ZhuanZhenJL.class);
        docDataModels.put("HSDB01.03", ErTongJKTJ.class);
        docDataModels.put("HSDC00.04", HuiZhenJL.class);



    }

    Validator validator;
    Map<String, String> exceptionMap;
    List<MiscValid.MiscCodeObject> codeObject;

    public CdaValidator() {
        this.exceptionMap = new LinkedHashMap<>();
        this.codeObject = new ArrayList<>();
        try {
//            validator = factory.newSchema(new File(Objects.requireNonNull(this.getClass().getResource(xsdPath)).toURI().getPath())).newValidator();
            validator = factory.newSchema(new File(xsdPath)).newValidator();
            validator.setErrorHandler(new ErrorHandler() {
                @Override
                public void warning(SAXParseException exception) throws SAXException {
                    log.error("验证警告", exception);
                }


                @Override
                public void error(SAXParseException exception) throws SAXException {
                    String line = "第" + exception.getLineNumber() + "行";
                    String message = exception.getMessage().split(":")[1];
                    if (exceptionMap.containsKey(line)) {
                        String s = exceptionMap.get(line);
                        s += "\n" + message;
                        exceptionMap.put(line, s);
                    } else {
                        exceptionMap.put(line, message);
                    }
                }

                @Override
                public void fatalError(SAXParseException exception) throws SAXException {
                    log.error("验证致命错误", exception);
                }
            });
        } catch (SAXException e) {
            e.printStackTrace();
            throw new CDAException("未获取xsd文件", e);
        } catch (Exception e) {
            e.printStackTrace();
            throw new CDAException("获取xsd文件错误", e);
        }

    }

    /**
     * @Description 验证CDA文档格式和值
     */
    public void validate(String cdaDoc) {
        //todo 目前只校验 文档内容最大值， 文档格式， 文档内容
        QianZhiJDApplication qianZhiJDApplication = ApplicationContextUtil.getBean("qianZhiJDApplication");
        QianZhiJDPZWJDto config = qianZhiJDApplication.getPeiZhi();
        if (null == config)  throw new CDAException("加载前置节点配置异常,请联系管理员", new RuntimeException());
        validateCdaDocSize(cdaDoc, config);
        if (config.isShiFouWDGSJY()) {
            validateFormat(cdaDoc);
        }
        if (config.isShiFouWDNRJY() && ObjectUtils.isEmpty(exceptionMap)) {
            JXDocument jxDocument = JXDocument.create(Parser.xmlParser().parseInput(cdaDoc, ""));
            JXNode root = jxDocument.selNOne("//ClinicalDocument");
            String code = root.selOne("/code/@code").asString();
            Class<? extends ClinicalDocumentData> aClass = docDataModels.get(code);
            validateValue(root, aClass, "");
        }
    }

    private void validateCdaDocSize(String cdaDoc, QianZhiJDPZWJDto config) {
        int cdaDocMaxSize = 0;
        if ("M".equals(config.getWenDangZDZDW())) {
            cdaDocMaxSize = config.getWenDangZDZ() * 1024 * 1024; //兆
        } else if ("K".equals(config.getWenDangZDZDW())) {
            cdaDocMaxSize = config.getWenDangZDZ() * 1024;         //K
        } else if ("b".equals(config.getWenDangZDZDW())) {
            cdaDocMaxSize = config.getWenDangZDZ() ;            //b
        } else {
            cdaDocMaxSize = -1;
        }
        int cdaDocSize  = cdaDoc.getBytes().length;
        if (cdaDocMaxSize >0 && cdaDocSize > cdaDocMaxSize) {
            throw new CDAException(String.format("CDA文档大小超过了设置值:【%s】 请重新上传!",
                    config.getWenDangZDZ()+config.getWenDangZDZDW()), new RuntimeException());
        }
    }

    /**
     * 验证CDA文档值
     *
     * @param root
     * @param dataModelCls
     * @param basePath
     */
    public void validateValue(JXNode root, Class dataModelCls, String basePath) {
        for (Field field : ReflectUtils.getAllFields(dataModelCls)) {
            Class<?> type = field.getType();
            if (type == null || type.getPackage() == null) {
                log.error(field.getName());
                continue;
            }
            String pkgName = type.getPackage().getName();
            XPath xPath = field.getAnnotation(XPath.class);
            String path = xPath.path();
            String codePath = StringUtil.isNullOrEmpty(xPath.codePath())?xPath.codePath():xPath.codePath()+"/..";
            try {
                if (Objects.nonNull(xPath)) {
                    XValid xValid = xPath.xValid();
                    String validType = xValid.type();
                    if (!type.equals(List.class) && !pkgName.startsWith("com.jy.rhin.model")) { //不处理list和自定义类型
                        JXNode jxNode = getNode(xPath, root);
                        if (Objects.nonNull(jxNode)&& !StringUtil.isNullOrEmpty(jxNode.asString())) {       //只做验证值，可以通过格式验证 说明节点可以不存在
                            if ("none".equals(validType)) {
                                this.exceptionMap.put(basePath + path, "验证成功");
                                continue;
                            }
                            AbsValid absValid;
                            if ("node".equals(validType)) {
                                for (Class<? extends AbsValid> aClass : xValid.nodeValidator()) {
                                    absValid = ValidFactory.get(aClass);   //找到对应的node验证器验证
                                    String msg = absValid.validate(xValid, jxNode.asString());
                                    if (!StringUtils.isEmpty(msg)) {  //校验到错误 中断验证
                                        this.exceptionMap.put(basePath + codePath+path, msg);
                                        break;
                                    }
                                }
                            } else if ("dataElement".equals(validType)) {
                                validDataElement(xPath, jxNode.asString(), basePath + codePath + xPath.path());
                            }
                        } else if (!xValid.nullable() && !"none".equals(validType)) {
                            this.exceptionMap.put(basePath +codePath+ path, "当前文档不存在该节点");
                        }
                    } else if (pkgName.startsWith("com.jy.rhin.model")) {
                        JXNode jxNode = getNode(xPath, root);
                        if (Objects.nonNull(jxNode)) {
                            if (type.equals(CodeObject.class) && !"none".equals(validType)) {  //CodeObject配置了数据元验证 默认只验证code值
                               // validDataElement(xPath, jxNode.selOne("/@code").asString(), basePath + codePath+ xPath.path());
                                validMisc(basePath +codePath+ path, new MiscValid.MiscCodeObject(jxNode, basePath),xPath.xValid().dataElement().getDataElement().getIdentifier());
                            } else if (type.equals(ValueUnit.class)) {
                                validDataElement(xPath, jxNode.selOne("/@value").asString(), basePath+ codePath + xPath.path());
                            } else {
                                validateValue(jxNode, field.getType(), basePath+codePath+xPath.path());
                            }
                        } else if (!xValid.nullable() && !"none".equals(validType)) {
                            this.exceptionMap.put(basePath +codePath+ path, "当前文档不存在该节点");
                        }
                    } else if (type.equals(List.class)) {
                        //获取list泛型类型
                        Class<?> rawClass = ReflectUtils.getListRawClass(field);
                        try {
                            if (Objects.nonNull(rawClass)) {
                                JXNode jxNode = root;
                                if (!StringUtils.isEmpty(xPath.path())) {
                                    jxNode = root.selOne(xPath.path());
                                }
                                String listItemPath = xPath.listItemPath();
                                if (!StringUtils.isEmpty(listItemPath)) {
                                    if(jxNode != null) {
                                        List<JXNode> itemNodes = jxNode.sel(listItemPath);
                                        if(ObjectUtils.isEmpty(itemNodes)
                                         &&!xValid.nullable() && !"none".equals(validType)){
                                            this.exceptionMap.put(basePath +codePath+ path+listItemPath, "当前文档不存在该节点");
                                        }
                                        for (JXNode itemNode : itemNodes) {
                                            Element element = itemNode.asElement();
                                            String backpath = "";
                                            for (int i = 0; i < xPath.listItemBackLevel(); i++) {
                                                element = element.parent();
                                                backpath += "/..";
                                            }
                                            if (!rawClass.getPackage().getName().startsWith("com.jy.rhin.model")) {   //列表中的泛型是非自定义属性 直接用列表上的注解进行验证
                                                Selector selector = ParserFactory.getParser(xPath);
                                                validDataElement(xPath, selector.selectValue(new JXNode(element), xPath), basePath + xPath.path() + xPath.listItemPath()+backpath);
                                            } else {
                                                validateValue(new JXNode(element), rawClass, basePath + xPath.path()+ xPath.listItemPath()+backpath);
                                            }
                                        }
                                    }
                                } else {
                                    log.error("列表字段{}未配置listItemPath", field.getName());
                                }
                            } else {
                                log.error("list未指定泛型，无法处理");
                            }
                        } catch (Exception e) {
                            log.error("字段{}处理list失败", field.getName(), e);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                log.error("验证错误:{}-{}", field.getName(), xPath.path());
                throw new CDAException("验证错误", e);
            }
            if (exceptionMap.get(basePath + path) == null) {
                this.exceptionMap.put(basePath + path, "验证成功");
            }
        }
    }

    /**
     * @Description 验证数据元
     */
    void validDataElement(XPath xPath, String value, String basePath) {
        String msg = ValidFactory.get(DataElementValid.class).validate(xPath.xValid(), value);
        if (!StringUtils.isEmpty(msg)) {
            this.exceptionMap.put(basePath,"格式校验错误,"+ msg+":"+value+",数据元:"+xPath.xValid().dataElement().getDataElement().getIdentifier());
        }
    }

    void validMisc(String basePath, MiscValid.MiscCodeObject code,String bsf) {
        Result<Boolean> result = ValidFactory.getMiscValid().valid(code);
        if (!"1".equals(result.getCode())) {
            this.exceptionMap.put(basePath, "值域校验错误,"+ result.getMsg()+":"+code.getCode()+"|"+code.getDisplayName() +",数据元:"+bsf);
        }
    }

    /**
     * @Description 获取注解配置的指定节点值 只考虑结构对的情况 如果是xml格式不对 则由格式校验出错误
     */
    JXNode getNode(XPath xPath, JXNode root) {
        JXNode jxNode = null;
        String path = xPath.path();
        if (path.endsWith("/text")) { // /text特殊处理 上级目录转换成element找到tag=text取值 转换为jxnode
            String prePath = path.substring(0, path.length() - 5);
            JXNode preNode = root.selOne(prePath);
            if (Objects.nonNull(preNode)) {
                jxNode = new JXNode(preNode.asElement().parent().getElementsByTag("text").text());
            }
        } else {
            String model = xPath.model();
            if ("code".equals(model)) {
                JXNode codeNode = root.selOne(xPath.codePath());
                if (Objects.nonNull(codeNode)) {
                    jxNode = new JXNode(codeNode.asElement().parent()).selOne(path);
                }
            } else if ("direct".equals(model)) {
                jxNode = root.selOne(path);
            }
        }
        return jxNode;
    }

    /**
     * @Description 验证CDA文档格式
     */
    public void validateFormat(String cdaDoc) {
        try {
            validator.validate(new StreamSource(IOUtils.toInputStream(cdaDoc)));
        } catch (
                Exception e) {
            log.error("验证错误", e);
        }
    }


    /**
     * @Description 获取异常信息
     */
    public String getExceptionMessage() {
        StringBuilder msgBuilder = new StringBuilder();
        for (Map.Entry<String, String> entry : exceptionMap.entrySet()) {
            msgBuilder.append(entry.getKey()).append(":")
                    .append(entry.getValue()).append("\n");
        }
        return msgBuilder.toString();
    }

    public Map<String, String> getExceptionMap() {
        return exceptionMap;
    }
}
